package fr.insalyon.sims.broadcast.image;

import fr.insalyon.sims.SIMSImageUtils;
import fr.insalyon.sims.broadcast.network.DataEvent;
import fr.insalyon.sims.broadcast.network.DataListener;
import fr.insalyon.sims.broadcast.network.IStream;
import fr.insalyon.sims.broadcast.network.IStreamTransmitter;

public class ImageInputBuffer implements DataListener {
	// ------------------------------------------------------- CONSTANTS -- //
	private static final int HEADER_SIZE = 24;
	private static final int DATA_HEADER_SIZE = 16;
	private static final int USEFUL_SIZE = IStream.DEFAULT_BUFFER_SIZE - 16;
	// ------------------------------------------------------ ATTRIBUTES -- //
	
	private IStreamTransmitter transmitter;
	private int currentImageId;
	private int currentImageSize;
	private int currentpacketPosition;
	private int receivedSize;
	
	// ---------------------------------------------------- CONSTRUCTORS -- //
	
	public ImageInputBuffer(IStreamTransmitter transmitter) {
		this.transmitter = transmitter;
		initialiseImage();
	}
	
	// --------------------------------------------------------- METHODS -- //
	// TODO: Fix bug in setInfoImage !!!!!
	public void dataReceived(DataEvent event) {
		byte[] data = event.getData();
		int packetSize = 0;
		
		// first packet of image data
		if (currentImageId == -1) {
			setImageInfo(data, event.getOffset());
			packetSize = event.getLength();
			receivedSize = packetSize  - HEADER_SIZE;
			currentpacketPosition = 0;
		} else {
			// currentpacketPosition is updated in sendData
			packetSize = Math.min(event.getLength(), currentImageSize - receivedSize);
			receivedSize += packetSize;
		}
		
		sendData(data, event.getOffset(), packetSize);

		if (receivedSize == currentImageSize) {
			initialiseImage();
		}
		
		// if there is data left in the same packet
		if (packetSize != event.getLength()) {
			dataReceived(new DataEvent(
					event.getSource(), data,
					event.getOffset() + packetSize,
					event.getLength() - packetSize));
			
		}
	}
	
	// ------------------------------------------------- PRIVATE METHODS -- //
	protected void initialiseImage() {
		currentImageId = -1;
	}
	
	protected void setImageInfo(byte[] data, int offset) {
		byte[] header = new byte[HEADER_SIZE];
		System.arraycopy(data, offset, header, 0, HEADER_SIZE);
		currentImageId = SIMSImageUtils.getRankFromByteArray(header);
		currentImageSize = SIMSImageUtils.getSizeFromByteArray(header);
	}
	
	protected byte[] getDataHeader(int imageId, int imageSize, int packetPosition, int packetSize) {
		byte[] header = new byte[DATA_HEADER_SIZE];
		
		for (int j = 0; j < 4; j++)
			header[3-j] = (byte) ((imageId >> (j * 8)) & 0xff);
		for (int j = 0; j < 4; j++)
			header[7-j] = (byte) ((imageSize >> (j * 8)) & 0xff);
		for (int j = 0; j < 4; j++)
			header[11-j] = (byte) ((packetPosition >> (j * 8)) & 0xff);
		for (int j = 0; j < 4; j++)
			header[15-j] = (byte) ((packetSize >> (j * 8)) & 0xff);
		
		return header;
	}
	
	protected void modifyPacketHeader(byte[] header, int packetPosition, int packetSize) {
		for (int j = 0; j < 4; j++)
			header[11-j] = (byte) ((packetPosition >> (j * 8)) & 0xff);
		for (int j = 0; j < 4; j++)
			header[15-j] = (byte) ((packetSize >> (j * 8)) & 0xff);
	}
	
	
	protected void sendData(byte[] data, int offset, int packetSize) {
		
		byte[] dataHeader = getDataHeader(currentImageId, currentImageSize, currentpacketPosition, packetSize);
		
		
		// if the packet is bigger than the default buffer size, cut this packet into many packets
		if (packetSize > USEFUL_SIZE) {
			int packets = (int) Math.ceil(packetSize / (double) (USEFUL_SIZE));
			
			for (int i = 0; i < packets; i++) {
				// Create new array in order to put the number of the frame at the
				// beginning of data array
				int newLength = i == packets - 1 ? // is last packet ?
					packetSize - USEFUL_SIZE * i : // last packet - size depends on length
					USEFUL_SIZE ; 				   // 2ko packet
				// modify data header
				modifyPacketHeader(dataHeader,currentpacketPosition, newLength);
				
				currentpacketPosition += newLength;
				
				byte[] newData = new byte[newLength + 16];
				// copy the header
				System.arraycopy(dataHeader, 0, newData, 0, DATA_HEADER_SIZE);
				// copy the real data without image header
				System.arraycopy(data, i*USEFUL_SIZE + offset, newData, DATA_HEADER_SIZE, newLength);
				// Send packet
				this.transmitter.send(newData, 0, newData.length);
				
			}
		}
		else { // just send the packet by adding header information
			byte[] newData = new byte[packetSize + 16];
			// copy the header
			System.arraycopy(dataHeader, 0, newData, 0, DATA_HEADER_SIZE);
			// copy the real data without image header
			System.arraycopy(data, offset, newData, DATA_HEADER_SIZE, packetSize);
			
			this.transmitter.send(newData, 0, newData.length);
			
			currentpacketPosition += packetSize;
		}
	}
	// --------------------------------------------------- INNER CLASSES -- //
}
